/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file    quadspi.c
 * @brief   This file provides code for the configuration
 *          of the QUADSPI instances.
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "quadspi.h"
#include "stm32l4xx_hal.h"
#include "cevent.h"
#include "cmsis_os.h"
#include <stdio.h>
#include <string.h>

#define FLASH_SFUD (0)
#if FLASH_SFUD
#include "sfud.h"
#endif

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

QSPI_HandleTypeDef hqspi;
#define QSPIHandle hqspi

__IO uint8_t CmdCplt, RxCplt, TxCplt, StatusMatch;

#if (FLASH_SFUD)
/* USER CODE BEGIN 0 */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data);

#define SFUD_DEMO_TEST_BUFFER_SIZE                     1024
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
#else
/* Buffer used for transmission */
uint8_t aTxBuffer[BUFFERSIZE] = "Memory-mapped QSPI communication";
uint8_t g_read_buf[1024] = {0};
#endif

#if defined(__CC_ARM)
extern uint32_t Load$$xip$$Base;
extern uint32_t Load$$xip$$Length;
#endif
static void xip_code_print(void);

/* QUADSPI init function */
void MX_QUADSPI_Init(void)
{
    hqspi.Instance = QUADSPI;
    hqspi.Init.ClockPrescaler = 0;
    hqspi.Init.FifoThreshold = 4;
    hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE;
    hqspi.Init.FlashSize = 23;
    hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
    hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
    hqspi.Init.FlashID = QSPI_FLASH_ID_1;
    hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
    if (HAL_QSPI_Init(&hqspi) != HAL_OK) {
        Error_Handler(__FUNCTION__,__LINE__);
    }
}

void HAL_QSPI_MspInit(QSPI_HandleTypeDef *qspiHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    static DMA_HandleTypeDef hdma;

    if (qspiHandle->Instance == QUADSPI) {
        /* USER CODE BEGIN QUADSPI_MspInit 0 */

        /* USER CODE END QUADSPI_MspInit 0 */
        /* QUADSPI clock enable */
        __HAL_RCC_QSPI_CLK_ENABLE();

        __HAL_RCC_QSPI_FORCE_RESET();
        __HAL_RCC_QSPI_RELEASE_RESET();

        __HAL_RCC_GPIOE_CLK_ENABLE();

        /* Enable DMA clock */
        __HAL_RCC_DMA1_CLK_ENABLE();

        /**QUADSPI GPIO Configuration
        PE10     ------> QUADSPI_CLK
        PE11     ------> QUADSPI_BK1_NCS
        PE12     ------> QUADSPI_BK1_IO0
        PE13     ------> QUADSPI_BK1_IO1
        PE14     ------> QUADSPI_BK1_IO2
        PE15     ------> QUADSPI_BK1_IO3
        */
        GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
        HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

        /* QUADSPI interrupt Init */
        HAL_NVIC_SetPriority(QUADSPI_IRQn, 5, 0);
        HAL_NVIC_EnableIRQ(QUADSPI_IRQn);
        
        /*##-4- Configure the DMA channel ###########################################*/
        /* QSPI DMA channel configuration */
        hdma.Init.Request             = DMA_REQUEST_5;
        hdma.Init.PeriphInc           = DMA_PINC_DISABLE;
        hdma.Init.MemInc              = DMA_MINC_ENABLE;
        hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
        hdma.Init.Mode                = DMA_NORMAL;
        hdma.Init.Priority            = DMA_PRIORITY_LOW;
        hdma.Instance                 = DMA1_Channel5;

        __HAL_LINKDMA(qspiHandle, hdma, hdma);
        HAL_DMA_Init(&hdma);

        /* NVIC configuration for DMA interrupt */
        HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0x00, 0);
        HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
    }
}

void HAL_QSPI_MspDeInit(QSPI_HandleTypeDef *qspiHandle)
{

    if (qspiHandle->Instance == QUADSPI)
    {
        /* USER CODE BEGIN QUADSPI_MspDeInit 0 */

        /* USER CODE END QUADSPI_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_QSPI_CLK_DISABLE();

        /**QUADSPI GPIO Configuration
        PE10     ------> QUADSPI_CLK
        PE11     ------> QUADSPI_BK1_NCS
        PE12     ------> QUADSPI_BK1_IO0
        PE13     ------> QUADSPI_BK1_IO1
        PE14     ------> QUADSPI_BK1_IO2
        PE15     ------> QUADSPI_BK1_IO3
        */
        HAL_GPIO_DeInit(GPIOE, GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);

        /* QUADSPI interrupt Deinit */
        HAL_NVIC_DisableIRQ(QUADSPI_IRQn);
        /* USER CODE BEGIN QUADSPI_MspDeInit 1 */

        /* USER CODE END QUADSPI_MspDeInit 1 */
    }
}

/**
  * @brief  Command completed callbacks.
  * @param  hqspi: QSPI handle
  * @retval None
  */
void HAL_QSPI_CmdCpltCallback(QSPI_HandleTypeDef *hqspi)
{
    CmdCplt++;
}

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  hqspi: QSPI handle
  * @retval None
  */
void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
    RxCplt++;
}

/**
  * @brief  Tx Transfer completed callbacks.
  * @param  hqspi: QSPI handle
  * @retval None
  */
void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
    TxCplt++; 
}

/**
  * @brief  Status Match callbacks
  * @param  hqspi: QSPI handle
  * @retval None
  */
void HAL_QSPI_StatusMatchCallback(QSPI_HandleTypeDef *hqspi)
{
    StatusMatch++;
}

/**
 * @brief  This function send a Write Enable and wait it is effective.
 * @param  hqspi: QSPI handle
 * @retval None
 */
static void QSPI_WriteEnable(QSPI_HandleTypeDef *hqspi)
{
    QSPI_CommandTypeDef sCommand;
    QSPI_AutoPollingTypeDef sConfig;

    /* Enable write operations ------------------------------------------ */
    sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    sCommand.Instruction = WRITE_ENABLE_CMD;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    sCommand.DataMode = QSPI_DATA_NONE;
    sCommand.DummyCycles = 0;
    sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
    sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler(__FUNCTION__,__LINE__);
    }

    /* Configure automatic polling mode to wait for write enabling ---- */
    sConfig.Match = 0x02;
    sConfig.Mask = 0x02;
    sConfig.MatchMode = QSPI_MATCH_MODE_AND;
    sConfig.StatusBytesSize = 1;
    sConfig.Interval = 0x10;
    sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

    sCommand.Instruction = READ_STATUS_REG_CMD;
    sCommand.DataMode = QSPI_DATA_1_LINE;

    if (HAL_QSPI_AutoPolling(hqspi, &sCommand, &sConfig, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler(__FUNCTION__,__LINE__);
    }
}

/**
 * @brief  This function read the SR of the memory and wait the EOP.
 * @param  hqspi: QSPI handle
 * @retval None
 */
static void QSPI_AutoPollingMemReady(QSPI_HandleTypeDef *hqspi)
{
    QSPI_CommandTypeDef sCommand;
    QSPI_AutoPollingTypeDef sConfig;

    /* Configure automatic polling mode to wait for memory ready ------ */
    sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    sCommand.Instruction = READ_STATUS_REG_CMD;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.DummyCycles = 0;
    sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
    sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    sConfig.Match = 0x00;
    sConfig.Mask = 0x01;
    sConfig.MatchMode = QSPI_MATCH_MODE_AND;
    sConfig.StatusBytesSize = 1;
    sConfig.Interval = 0xFFFE;
    sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

    if (HAL_QSPI_AutoPolling_IT(hqspi, &sCommand, &sConfig) != HAL_OK)
    {
        Error_Handler(__FUNCTION__,__LINE__);
    }
}

/**
 * @brief  This function enablethe quad modeon the QSPI memory
 * @param  hqspi: QSPI handle
 * @retval None
 */
static uint8_t QSPI_QuadMode(QSPI_HandleTypeDef *hqspi)
{
    QSPI_CommandTypeDef sCommand;
    QSPI_AutoPollingTypeDef sConfig;
    uint8_t reg;

    /* Read status register */
    sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    sCommand.Instruction = READ_STATUS_REG_CMD;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.DummyCycles = 0;
    sCommand.NbData = 1;
    sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
    sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        return HAL_ERROR;
    }

    if (HAL_QSPI_Receive(hqspi, &reg, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        return HAL_ERROR;
    }

    /* Enable write operations */
    QSPI_WriteEnable(hqspi);

    SET_BIT(reg, 0x40);

    sCommand.Instruction = WRITE_STATUS_CFG_REG_CMD;

    if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        return HAL_ERROR;
    }

    if (HAL_QSPI_Transmit(hqspi, &reg, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        return HAL_ERROR;
    }

    /* Wait that memory is ready */

    /* Configure automatic polling mode to wait for memory ready ------ */
    sCommand.Instruction = READ_STATUS_REG_CMD;

    sConfig.Match = 0x00;
    sConfig.Mask = 0x01;
    sConfig.MatchMode = QSPI_MATCH_MODE_AND;
    sConfig.StatusBytesSize = 1;
    sConfig.Interval = 0x9FFF;
    sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

    if (HAL_QSPI_AutoPolling(hqspi, &sCommand, &sConfig, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        return HAL_ERROR;
    }

    return HAL_OK;
}

#if (FLASH_SFUD)
/* USER CODE BEGIN 4 */
/**
 * SFUD demo for the first flash device test.
 *
 * @param addr flash start address
 * @param size test flash size
 * @param size test flash data buffer
 */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data)
{
    sfud_err result = SFUD_SUCCESS;
    extern sfud_flash *sfud_dev;
    const sfud_flash *flash = sfud_get_device(SFUD_W25_DEVICE_INDEX);
    size_t i;
    /* prepare write data */
    for (i = 0; i < size; i++)
    {
        data[i] = i;
    }
    /* erase test */
    result = sfud_erase(flash, addr, size);
    if (result == SFUD_SUCCESS)
    {
        printf("Erase the %s flash data finish. Start from 0x%08X, size is %zu.\r\n", flash->name, addr, size);
        return;
    }
    else
    {
        printf("Erase the %s flash data failed.\r\n", flash->name);
        return;
    }
    /* write test */
    result = sfud_write(flash, addr, size, data);
    if (result == SFUD_SUCCESS)
    {
        printf("Write the %s flash data finish. Start from 0x%08X, size is %zu.\r\n", flash->name, addr, size);
    }
    else
    {
        printf("Write the %s flash data failed.\r\n", flash->name);
        return;
    }
    /* read test */
    result = sfud_read(flash, addr, size, data);
    if (result == SFUD_SUCCESS)
    {
        printf("Read the %s flash data success. Start from 0x%08X, size is %zu. The data is:\r\n", flash->name, addr, size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
        for (i = 0; i < size; i++)
        {
            if (i % 16 == 0)
            {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", data[i]);
            if (((i + 1) % 16 == 0) || i == size - 1)
            {
                printf("\r\n");
            }
        }
        printf("\r\n");
    }
    else
    {
        printf("Read the %s flash data failed.\r\n", flash->name);
    }
    /* data check */
    for (i = 0; i < size; i++)
    {
        if (data[i] != i % 256)
        {
            printf("Read and check write data has an error. Write the %s flash data failed.\r\n", flash->name);
            break;
        }
    }
    if (i == size)
    {
        printf("The %s flash test is success.\r\n", flash->name);
    }
}

void qspi_task_sfud(void *param)
{
    sfud_err sfud_ret = sfud_init();
    if (SFUD_SUCCESS != sfud_ret) {
        printf("sfud init failed to:%d.\r\n", sfud_ret);
    } else {
        /* enable qspi fast read mode, set four data lines width */
        sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25_DEVICE_INDEX), 4);
        sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
    }

    while (1) {
        osDelay(100);
    }
}
#else
/**
 * @brief qspi 任务
 * 
 * 
 */
void qspi_task(void *param)
{
    QSPI_CommandTypeDef      sCommand;
    QSPI_MemoryMappedTypeDef sMemMappedCfg;
    uint32_t address = 0;
    __IO uint8_t *qspi_addr = (__IO uint8_t *)(0x90000000);
    uint16_t index;
    __IO uint8_t step = 0;
    uint8_t *flash_addr = 0;
    uint32_t section_length;
    printf("qspi task start.\r\n");

    sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    sCommand.AddressSize       = QSPI_ADDRESS_24_BITS;
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;
    sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    while(1) {
        if (step < 5) {
            printf("qspi test step:%d.\r\n", step);
        }
        switch(step) {
            case 0: {
                CmdCplt = 0;

                /* Initialize QuadSPI ------------------------------------------------ */
                HAL_QSPI_DeInit(&QSPIHandle);
                if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
                {
                    Error_Handler(__FUNCTION__,__LINE__);
                }

                /* Activate the Quad mode on the memory */
                if (QSPI_QuadMode(&QSPIHandle) != HAL_OK)
                {
                    Error_Handler(__FUNCTION__,__LINE__);
                }

                /* Enable write operations ------------------------------------------- */
                QSPI_WriteEnable(&QSPIHandle);

                /* Erasing Sequence -------------------------------------------------- */
                sCommand.Instruction = BLOCK_ERASE_CMD;
                sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
                sCommand.Address     = address;
                sCommand.DataMode    = QSPI_DATA_NONE;
                sCommand.DummyCycles = 0;

                if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)
                {
                    Error_Handler(__FUNCTION__,__LINE__);
                }

                step++;
            } break;

            case 1: {
                if(CmdCplt != 0) {
                    CmdCplt = 0;
                    StatusMatch = 0;

                    /* Configure automatic polling mode to wait for end of erase ------- */  
                    QSPI_AutoPollingMemReady(&QSPIHandle);

                    step++;
                }
            } break;
        
            case 2: {
                if(StatusMatch != 0) {
                    StatusMatch = 0;
                    TxCplt = 0;
                    
                    /* Enable write operations ----------------------------------------- */
                    QSPI_WriteEnable(&QSPIHandle);

                    for (int i = 0; i < BUFFERSIZE; i++) {
                        aTxBuffer[i] = i;
                    }

                    /* Writing Sequence ------------------------------------------------ */
                    sCommand.Instruction = QUAD_PAGE_PROG_CMD;
                    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;//QSPI_ADDRESS_4_LINES
                    sCommand.DataMode    = QSPI_DATA_4_LINES;
                    sCommand.Address     = 0;
                    sCommand.NbData      = BUFFERSIZE;

                    if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
                    {
                        Error_Handler(__FUNCTION__,__LINE__);
                    }

#if 0
                    if (HAL_QSPI_Transmit_DMA(&QSPIHandle, aTxBuffer) != HAL_OK)
                    {
                        Error_Handler(__FUNCTION__,__LINE__);
                    }
#else
#if defined(__CC_ARM)
                    flash_addr = (uint8_t *)(&Load$$xip$$Base);
#endif
                    if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) {
                        Error_Handler(__FUNCTION__,__LINE__);
                    }
#endif

                    step++;
                }
            } break;

            case 3: {
                if (TxCplt != 0) {
                    TxCplt = 0;
                    StatusMatch = 0;

                    /* Configure automatic polling mode to wait for end of program ----- */
                    QSPI_AutoPollingMemReady(&QSPIHandle);

                    step++;
                }
            } break;

            case 4: {
                if (StatusMatch != 0) {
                    StatusMatch = 0;
                    RxCplt = 0;

                    /* Reading Sequence ------------------------------------------------ */
                    sCommand.Instruction = QUAD_INOUT_READ_CMD;
                    sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;
                    sCommand.AddressMode = QSPI_ADDRESS_4_LINES;//QSPI_ADDRESS_1_LINE

                    sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;

                    if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)
                    {
                        Error_Handler(__FUNCTION__,__LINE__);
                    }

                    printf("qspi_addr:%x.\r\n", qspi_addr);
                    printf("xip code flash load addr:%x.\r\n", flash_addr);
                    section_length = (uint32_t)&Load$$xip$$Length;
                    printf("qspi xip section length:%x.\r\n", section_length);
                    memcpy(g_read_buf, (const void*)qspi_addr, BUFFERSIZE);

                    step++;
                }
            } break;

            case 5: {
                /* execute qspi code */
                xip_code_print();
                //osDelay(1000);
                //printf("flash code print.\r\n");
            } break;

            default: Error_Handler(__FUNCTION__,__LINE__); break;
        }
        osDelay(100);
    }
}
#endif

uint32_t qspi_task_param = 0;
/**
 * 
 */
void qspi_task_init(void)
{
#if FLASH_SFUD
int32_t ret = xTaskCreate(qspi_task_sfud, "qspi_task_sfud", 512, &qspi_task_param, 5, NULL);
#else
    int32_t ret = xTaskCreate(qspi_task, "qspi_task", 512, &qspi_task_param, 5, NULL);
#endif
    if (ret != pdPASS) {
        printf("qspi task creat failed:%d.\r\n", ret);
    }
}
CEVENT_EXPORT(EVENT_INIT_STAGE2, qspi_task_init);

/* USER CODE BEGIN 1 */
/**
  * @brief  Toggle the GPIOs
  * @param  None
  * @retval None
  */
#if defined(__CC_ARM)
#pragma arm section code = ".qspi"
#pragma no_inline
static void xip_code_print(void)
#endif
{
    printf("xip code print.\r\n");
    osDelay(5000);
}
#if defined(__CC_ARM)
#pragma arm section code
#endif

/* USER CODE END 1 */
